#include "GammaContainer.h"
#include <math.h>
#include "../common/SerialInator.h"



GammaContainer::GammaContainer() : cantBitsBufferPrimario(0) { }


GammaContainer::~GammaContainer() {
	this->cantBitsBufferPrimario = 0;
}


bool GammaContainer::is_full() const{
	if (this->cantBitsBufferPrimario == this->buffPrimario.size())
		return true;
	else
		return false;
}


bool GammaContainer::is_empty() const{
	if (this->cantBitsBufferPrimario == 0)
		return true;
	else
		return false;
}


unsigned int GammaContainer::getCantBits() const{
	return this->cantBitsBufferPrimario;
}


unsigned int GammaContainer::getCantBytes() const{
	double val = this->cantBitsBufferPrimario / 8;
	return ceil(val);
}


void GammaContainer::clear(){
	this->buffPrimario.reset();
	this->cantBitsBufferPrimario = 0;
}


GammaContainer& GammaContainer:: operator<<(const unsigned int val){
	if (is_full()) throw std::runtime_error("El contenedor Gamma esta lleno");
	if (val == 0) throw std::runtime_error("El valor a codificar en Gamma debe ser mayor a cero");

	// Separa el valor en las dos partes del codigo Gamma de Elias
	int primerParte = static_cast<int>( log(static_cast<double>(val)) / log(2.0) );
	int segundaParte = val - static_cast<int>( pow(2.0, primerParte) );

	// Codifica las dos partes del codigo
	std::bitset<32> bs1 = codificarPrimerParte(primerParte);
	std::bitset<32> bs2(segundaParte);

	// El valor comprimido es "(bs1 << primerParte) | bs2", hay que agregarlo al contenedor principal
	agregarContenedor((bs1 << primerParte) | bs2);

	return *this;
}


GammaContainer& GammaContainer::operator>>(unsigned int &val){
	if (is_empty()) throw std::runtime_error("El contenedor Gamma esta vacio");

	// La primer parte son la cantidad de unos hasta encontrar el primer cero
	int posInicial = this->cantBitsBufferPrimario -1;
	int cantUnos = 0;
	while ( this->buffPrimario.test( posInicial - cantUnos ) ) ++cantUnos;

	// La segunda parte es "cantUnos" bits que se debe transformar en un numero binario
	std::bitset<32> bs;
	for (int i = 0; i < cantUnos; ++i){

		// En que posicion del buffer inicia la segunda parte de la codificacion
		int iniSegundaParte = posInicial - cantUnos;

		// En que posicion terminan los bits de la segunda parte de la codificacion
		int finSegundaParte = iniSegundaParte - cantUnos;

		bs[i] = this->buffPrimario[finSegundaParte + i];
	}

	// Se "unen" ambas partes
	unsigned int uno = static_cast<unsigned int>( pow(2.0, cantUnos) );
	unsigned int dos = bs.to_ulong();
	val = uno + dos;

	// Se actualiza la cantidad de bits
	this->cantBitsBufferPrimario -= 2*cantUnos + 1;

	return *this;
}



std::bitset<32> GammaContainer::codificarPrimerParte(const unsigned int val) const{
	// "val" cantidad de unos mas un cero al final
	std::bitset<32> bs;
	for (unsigned int i = 0; i < val; ++i){
		bs[0] = true;
		bs <<=1;
	}

	return bs;
}


void GammaContainer::agregarContenedor(std::bitset<32> bs){

	/** �Porque no se chequea la posicion 0 para ver si hay un bit seteado?
	 * Porque no existe ningun valor que se pueda codificar con un solo bit en 1;
	 * Con un solo bit en 0 es como se codifica el valor "1"
	 * */
	int pos = bs.size()-1;
	while ( pos >0 && !bs.test(pos) ) --pos;
	int cantBits = pos +1;

	// Chequea que haya suficiente espacio en el buffer principal
	int cantBitsLibres = this->buffPrimario.size() - this->cantBitsBufferPrimario;

	if (cantBits > cantBitsLibres){
		// Si no entran mas bits el contenedor esta lleno
		throw std::runtime_error("El contenedor de bits esta lleno");
	}

	// Concatena los bits al contenedor principal
	this->buffPrimario <<= cantBits;
	for (int i = 0; i < cantBits; ++i){
		this->buffPrimario[i] = bs[i];
	}

	// Actualiza la cantidad de bits
	this->cantBitsBufferPrimario += cantBits;
}

//---------------------------------------------------------------

void GammaContainer::load(std::ifstream &fin){
	fin.read(reinterpret_cast<char*>(&this->cantBitsBufferPrimario), sizeof(this->cantBitsBufferPrimario));
	fin >> this->buffPrimario;
}


void GammaContainer::store(std::ofstream &fout) const{
	fout.write(reinterpret_cast<const char*>(&this->cantBitsBufferPrimario), sizeof(this->cantBitsBufferPrimario));
	fout << this->buffPrimario;
}


int GammaContainer::serializar(char* arrBytes){
	SerialInator s;
	s.reset();
	s.serializar(arrBytes, this->cantBitsBufferPrimario);
	s.serializar<960>(arrBytes, this->buffPrimario);

	return s.getBufferSize();
}


int GammaContainer::desSerializar(char* arrBytes){
	SerialInator s;
	s.reset();
	s.desSerializar(arrBytes, this->cantBitsBufferPrimario);
	s.desSerializar<960>(arrBytes, this->buffPrimario);


	return s.getBufferSize();
}

